package jiang.jredis.starter;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.Expiry;
import jiang.jredis.cache.DoubleCacheManager;
import jiang.jredis.starter.annotation.EnableDoubleCaching;
import org.checkerframework.checker.index.qual.NonNegative;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;

import java.time.Duration;
import java.util.Stack;

/**
 * RedisDoubleCache相关配置
 * 只有添加了@EnableDoubleCaching注解才生效
 * 同时会使@EnableCaching注解生效
 * 1.Caffeine本地缓存
 * 2.DoubleCacheManager
 */
@Configuration
@ConditionalOnBean(annotation = EnableDoubleCaching.class)
@EnableCaching
public class RedisDoubleCacheAutoConfiguration {

    /**
     * 获取应用名配置信息
     */
    @Value("${spring.application.name}")
    private String applicationName;

    /**
     * 注入Caffeine本地缓存
     * 使用缓存过期栈ThreadLocal自定义过期时间
     * 设置软引用在OOM时清除缓存value
     * 开启缓存数据统计
     */
    @Bean
    @ConditionalOnMissingBean
    public Cache<Object,Object> nativeCache(ThreadLocal<Stack<Long>> cacheExpireStackThreadLocal) {
        return Caffeine.newBuilder()
                .expireAfter(new Expiry<Object, Object>() {
                    @Override
                    public long expireAfterCreate(@NonNull Object key, @NonNull Object value, long currentTime) {
                        Stack<Long> stack = cacheExpireStackThreadLocal.get();
                        if(stack.isEmpty()) {
                            throw new RuntimeException("缓存过期时间未指定");
                        }
                        long expireMillis = stack.peek();
                        long expireNanos = Duration.ofMillis(expireMillis).toNanos();
                        return expireNanos;
                    }
                    @Override
                    public long expireAfterUpdate(@NonNull Object key, @NonNull Object value, long currentTime, @NonNegative long currentDuration) {
                        return currentDuration;
                    }
                    @Override
                    public long expireAfterRead(@NonNull Object key, @NonNull Object value, long currentTime, @NonNegative long currentDuration) {
                        return currentDuration;
                    }
                })
                .softValues()
                .recordStats()
                .build();
    }

    /**
     * 注入DoubleCacheManager
     */
    @Bean
    public CacheManager cacheManager(Cache<Object,Object> nativeCache,
                                           RedisTemplate<String,Object> redisTemplate, RedissonClient redissonClient,
                                           ThreadLocal<Stack<Long>> cacheExpireStackThreadLocal,
                                           RedisMessageListenerContainer redisMessageListenerContainer) {
        return new DoubleCacheManager(applicationName, nativeCache,
                redisTemplate, (Redisson) redissonClient, cacheExpireStackThreadLocal, redisMessageListenerContainer);
    }

}
